﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using ZyGame.Transport.Common;
using System.Threading.Tasks;

namespace ZyGame.Transport.UDP
{
    public class KCPChannel : IChannel
    {
        protected UdpClient client;
        protected volatile bool running;
        protected Kcp kcp;
        protected IPEndPoint serverAddr;
        protected Object LOCK = new Object(); //加锁访问收到的数据
        protected Object SEND_LOCK = new Object(); //加锁访问发送列表
        protected LinkedList<ByteBuf> received;
        protected LinkedList<ByteBuf> sendList;
        protected int nodelay;
        protected int interval = Kcp.IKCP_INTERVAL;
        protected int resend;
        protected int nc;
        protected int sndwnd = Kcp.IKCP_WND_SND;
        protected int rcvwnd = Kcp.IKCP_WND_RCV;
        protected int mtu = Kcp.IKCP_MTU_DEF;
        protected volatile bool needUpdate;
        protected long timeout; //超时
        protected DateTime lastTime; //上次检测时间
        private IPEndPoint curAddr; //当前的客户端地址
        private List<IChannelHandler> networkOutput = new List<IChannelHandler>();
        private INetworkSerialized serialized;
        private int cid;
        private string address;
        private ushort port;

        public string uri
        {
            get
            {
                if (curAddr == null) return string.Empty;
                return curAddr.ToString();
            }
        }

        public bool IsConnected => running;

        /// <summary>
        /// 客户端
        /// </summary>
        public KCPChannel(int cid, string address, ushort port, INetworkSerialized serialized)
        {
            this.received = new LinkedList<ByteBuf>();
            this.sendList = new LinkedList<ByteBuf>();
            this.port = port;
            this.cid = cid;
            this.address = address;
            this.serialized = serialized;
            NoDelay(1, 10, 2, 1);
            WndSize(64, 64);
            Timeout(10 * 1000);
            SetMtu(512);
            SetMinRto(10);
        }

        public Task Connect()
        {
            if (address != null)
            {
                serverAddr = new IPEndPoint(IPAddress.Parse(address), port);
            }

            client = new UdpClient(port);
            kcp = new Kcp(cid, output, null);
            //mode setting
            kcp.SetConv(cid);
            kcp.NoDelay(nodelay, interval, resend, nc);
            kcp.WndSize(sndwnd, rcvwnd);
            kcp.SetMtu(mtu);
            try
            {
                if (serverAddr != null)
                {
                    this.client.Connect(serverAddr);
                }

                client.BeginReceive(Received, client);
                this.running = true;
                Thread t = new Thread(new ThreadStart(run)); //状态更新
                t.IsBackground = true;
                t.Start();
                Client.Tools.RunningInMainThread(() =>
                {
                    for (int i = 0; i < networkOutput.Count; i++)
                    {
                        networkOutput[i].HandleActived(this);
                    }
                });
            }
            catch (Exception ex)
            {
                Client.Tools.RunningInMainThread(() =>
                {
                    for (int i = 0; i < networkOutput.Count; i++)
                    {
                        this.networkOutput[i].HandleException(this, ex);
                    }
                });
            }

            return Task.CompletedTask;
        }

        public Task Close()
        {
            if (running)
            {
                running = false;
                try
                {
                    this.client.Close();
                    Client.Tools.RunningInMainThread(() =>
                    {
                        for (int i = 0; i < networkOutput.Count; i++)
                        {
                            networkOutput[i].HandleInactived(this);
                        }
                    });
                }
                catch (Exception ex)
                {
                }
            }

            return Task.CompletedTask;
        }

        private void run()
        {
            while (running)
            {
                DateTime st = DateTime.Now;
                this.Update();
                if (this.needUpdate)
                {
                    continue;
                }

                DateTime end = DateTime.Now;
                while ((end - st).TotalMilliseconds < 10)
                {
                    if (this.needUpdate)
                    {
                        break;
                    }

                    Thread.Sleep(0);
                    end = DateTime.Now;
                }
            }
        }

        /// <summary>
        /// 超时设定
        /// </summary>
        public void Timeout(long timeout)
        {
            this.timeout = timeout;
        }

        private void output(ByteBuf msg, Object user)
        {
            this.client.Send(msg.GetRaw(), msg.ReadableBytes());
        }

        private void Received(IAsyncResult ar)
        {
            UdpClient client = (UdpClient)ar.AsyncState;
            try
            {
                byte[] data = client.EndReceive(ar, ref this.curAddr);
                lock (LOCK)
                {
                    this.received.AddLast(new ByteBuf(data));
                    this.needUpdate = true;
                    this.lastTime = DateTime.Now;
                }

                client.BeginReceive(Received, ar.AsyncState);
            }
            catch (Exception ex)
            {
                Client.Tools.RunningInMainThread(() =>
                {
                    for (int i = 0; i < networkOutput.Count; i++)
                    {
                        this.networkOutput[i].HandleException(this, ex);
                    }
                });
            }
        }

        public void RemoveOutput(IChannelHandler handler)
        {
            networkOutput.Remove(handler);
        }

        public Task WriteAndFlush(object message)
        {
            ByteBuf byteBuf = serialized.Serialized(message);
            Send(byteBuf);
            return Task.CompletedTask;
        }

        /// <summary>
        /// 发送
        /// </summary>
        /// <param name="content"></param>
        private void Send(ByteBuf content)
        {
            lock (this.SEND_LOCK)
            {
                this.sendList.AddLast(content);
                this.needUpdate = true;
            }
        }

        /// <summary>
        /// 更新
        /// </summary>
        private void Update()
        {
            //input
            lock (LOCK)
            {
                while (this.received.Count > 0)
                {
                    ByteBuf bb = this.received.First.Value;
                    kcp.Input(bb);
                    this.received.RemoveFirst();
                }
            }

            //receive
            int len;
            while ((len = kcp.PeekSize()) > 0)
            {
                ByteBuf bb = new ByteBuf(len);
                int n = kcp.Receive(bb);
                if (n > 0)
                {
                    Client.Tools.RunningInMainThread(() =>
                    {
                        object message = serialized.Deserialized(bb);
                        for (int i = 0; i < networkOutput.Count; i++)
                        {
                            this.networkOutput[i].HandleReceive(this, message);
                        }
                    });
                }
            }

            //send
            lock (this.SEND_LOCK)
            {
                while (this.sendList.Count > 0)
                {
                    ByteBuf item = this.sendList.First.Value;
                    this.kcp.Send(item);
                    this.sendList.RemoveFirst();
                }
            }

            //update kcp status
            int cur = (int)DateTime.Now.Ticks;
            if (this.needUpdate || cur >= kcp.GetNextUpdate())
            {
                kcp.Update(cur);
                kcp.SetNextUpdate(kcp.Check(cur));
                this.needUpdate = false;
            }

            //check timeout
            if (this.timeout > 0 && lastTime != DateTime.MinValue)
            {
                double del = (DateTime.Now - this.lastTime).TotalMilliseconds;
                if (del > this.timeout)
                {
                    Client.Tools.RunningInMainThread(() =>
                    {
                        for (int i = 0; i < networkOutput.Count; i++)
                        {
                            this.networkOutput[i].HandleException(this, new TimeoutException());
                        }
                    });
                }
            }
        }

        public void AddOutput(IChannelHandler handler)
        {
            networkOutput.Add(handler);
        }

        private void NoDelay(int nodelay, int interval, int resend, int nc)
        {
            this.nodelay = nodelay;
            this.interval = interval;
            this.resend = resend;
            this.nc = nc;
        }

        /**
         * set maximum window size: sndwnd=32, rcvwnd=32 by default
         *
         * @param sndwnd
         * @param rcvwnd
         */
        private void WndSize(int sndwnd, int rcvwnd)
        {
            this.sndwnd = sndwnd;
            this.rcvwnd = rcvwnd;
        }

        /**
         * change MTU size, default is 1400
         *
         * @param mtu
         */
        private void SetMtu(int mtu)
        {
            this.mtu = mtu;
        }

        private bool IsStream()
        {
            return this.kcp.IsStream();
        }

        private void SetStream(bool stream)
        {
            this.kcp.SetStream(stream);
        }

        private void SetMinRto(int min)
        {
            this.kcp.SetMinRto(min);
        }

        private void SetConv(int conv)
        {
            this.kcp.SetConv(conv);
        }

        public Task<T> WriteAndFlush<T>(object meesage)
        {
            throw new NotImplementedException();
        }
    }
}